package com.linln.admin.pcs.service;

import com.github.binarywang.wxpay.bean.request.WxPayRefundRequest;
import com.github.binarywang.wxpay.bean.result.WxPayRefundResult;
import com.github.binarywang.wxpay.config.WxPayConfig;
import com.github.binarywang.wxpay.service.WxPayService;
import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl;
import com.linln.admin.util.WXPayUtil;
import com.linln.common.data.QuerySpec;
import com.linln.common.utils.SnowflakeIdGenerator;
import com.linln.modules.system.domain.*;
import com.linln.modules.system.repository.CompanyParamRepository;
import com.linln.modules.system.repository.OrderPayRepository;
import com.linln.modules.system.repository.OrderRepository;
import com.linln.modules.system.repository.PayRecordRepository;
import com.thoughtworks.xstream.XStream;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.ByteArrayInputStream;
import javax.net.ssl.SSLContext;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.math.BigDecimal;
import java.security.*;
import java.security.cert.CertificateException;
import java.util.*;

@Service
public class WechatPayRefundService {

    private final static Logger logger = LoggerFactory.getLogger(WechatPayRefundService.class);

    @Value("${wx.appId}")
    private String APPID;

    @Value("${wx.refundUrl}")
    private String REFUND_NOTIFY_URL;

    private static final String REFUND_URL = "https://api.mch.weixin.qq.com/secapi/pay/refund";

    @Autowired
    private CompanyParamRepository companyParamRepository;

    @Autowired
    private OrderRepository orderRepository;

    @Autowired
    private PayRecordRepository payRecordRepository;

    @Autowired
    private OrderPayRepository orderPayRepository;


    public Map<String,String> handleRefund(PayParam payParam) throws Exception {
        logger.error("开始退款:"+payParam);
        Map<String, String> msgMap = new HashMap<>();
        PayRecord payRecord = new PayRecord();
        payRecord.setStatus((byte) 1);
        //根据订单号查公司
        QuerySpec querySpec = QuerySpec.matching();
        querySpec.withMatcherValue("sn", QuerySpec.EQUAL, payParam.getOutTradeNo());
        List<Order> orderList = orderRepository.findAll(QuerySpec.of(querySpec));
        if (orderList.size()>0){
            Order order = orderList.get(0);
            //订单号
            String sn = order.getSn();
            payRecord.setOrderNo(sn);
            QuerySpec querySpec1 = QuerySpec.matching();
            querySpec1.withMatcherValue("companyId", QuerySpec.EQUAL, String.valueOf(order.getCompanyId()));
            querySpec1.withMatcherValue("status", QuerySpec.EQUAL, "1");
            List<CompanyParam> companyParamList = companyParamRepository.findAll(QuerySpec.of(querySpec1));
            if (companyParamList.size()>0){
                CompanyParam companyParam = companyParamList.get(0);
                payRecord.setCompanyId(String.valueOf(companyParam.getCompanyId()));
                if (ObjectUtils.isNotEmpty(companyParam.getApiKey())&&ObjectUtils.isNotEmpty(companyParam.getKeyPath())
                        &&ObjectUtils.isNotEmpty(companyParam.getMchId())){
                    Map<String, String> params = new HashMap<>();
                    params.put("appid", APPID);
                    params.put("mch_id", companyParam.getMchId());
                    params.put("op_user_id", companyParam.getMchId());
                    params.put("notify_url", REFUND_NOTIFY_URL);
                    //退款金额
                    String refundFee = payParam.getRefundFee();
                    //退款子订单集合
                    List<RefundTempParam> refundTempParams = checkChildOrderList(sn, refundFee);
                    for (RefundTempParam refundTempParam : refundTempParams) {
                        String outTradeNo = refundTempParam.getOutTradeNo();
                        params.put("nonce_str", generateNonceStr());
                        params.put("out_trade_no", outTradeNo);
                        //生成订单号
                        SnowflakeIdGenerator idGenerator = new SnowflakeIdGenerator(1, 1);
                        long l = idGenerator.nextId();
                        String outRefundNo = "TK"+l;
                        params.put("out_refund_no", outRefundNo);
                        //退款总金额
                        String totalFee = refundTempParam.getTotalFee();
                        double a = Double.parseDouble(totalFee);
                        double tfe = a*100;
                        int c = (int) tfe;
                        params.put("total_fee", String.valueOf(c));

                        String refundFee1 = refundTempParam.getRefundFee();
                        double b = Double.parseDouble(refundFee1);
                        double bfe = b*100;
                        int d = (int) bfe;
                        params.put("refund_fee", String.valueOf(d));

                        String sign = WXPayUtil.generateSign(params, companyParam.getApiKey());
                        params.put("sign", sign);
                        logger.error("退款参数:"+params);

                        String xmlData = mapToXml(params);
                        String responseXml = sendPostRequest(xmlData, companyParam.getKeyPath(), companyParam.getMchId());
                        Map<String, String> responseMap = parseXml(responseXml);
                        logger.error("退款解析结果:"+responseMap);

                        payRecord.setOrderChildNo(outTradeNo);
                        payRecord.setOutRefundNo(outRefundNo);
                        payRecord.setOutRefundFee(refundFee1);
                        payRecord.setResult(responseMap.toString());
                        payRecord.setType(payParam.getType());
                        payRecord.setPatientName(order.getPatientName());
                        payRecord.setOperatorStatus(2);

                        Byte refundStatus = order.getRefundStatus();
                        if (verifySign(responseMap, companyParam.getApiKey())) {
                            if ("SUCCESS".equals(responseMap.get("return_code")) && "SUCCESS".equals(responseMap.get("result_code"))) {
                                msgMap.put("code","1");
                                msgMap.put("msg","退款成功");
                                if (refundStatus==0){
                                    order.setRefundStatus((byte) 1);
                                    orderRepository.save(order);
                                }


                                payRecord.setMsg("退款成功");
                                payRecord.setOutStatus(1);
                                payRecord.setBackStatus(2);
                                payRecordRepository.save(payRecord);
                            } else {
                                msgMap.put("code","0");
                                msgMap.put("msg","退款失败：" + responseMap.get("err_code_des"));
                                if (refundStatus==0){
                                    order.setRefundStatus((byte) 3);
                                    order.setRefundRemark(responseMap.get("err_code_des"));
                                    orderRepository.save(order);
                                }
                                payRecord.setMsg("退款失败：" + responseMap.get("err_code_des"));
                                payRecord.setOutStatus(0);
                                payRecord.setBackStatus(0);
                                payRecordRepository.save(payRecord);
                            }
                        } else {
                            msgMap.put("code","0");
                            msgMap.put("msg","退款响应签名验证失败");
                            if (refundStatus==0){
                                order.setRefundStatus((byte) 3);
                                order.setRefundRemark("退款响应签名验证失败");
                                orderRepository.save(order);
                            }
                            payRecord.setMsg("退款响应签名验证失败");
                            payRecord.setOutStatus(0);
                            payRecord.setBackStatus(0);
                            payRecordRepository.save(payRecord);
                        }

                        Thread.sleep(1000);
                    }
                }else{
                    msgMap.put("code","0");
                    msgMap.put("msg","退款失败,公司商户信息错误");

                    payRecord.setMsg("退款失败,公司商户信息错误");
                    payRecord.setOutStatus(0);
                    payRecord.setBackStatus(0);
                    payRecordRepository.save(payRecord);
                }
                return msgMap;
            }else{
                msgMap.put("code","0");
                msgMap.put("msg","退款失败,公司信息不存在");

                payRecord.setMsg("退款失败,公司信息不存在");
                payRecord.setOutStatus(0);
                payRecord.setBackStatus(0);
                payRecordRepository.save(payRecord);
                return msgMap;
            }
        }else{
            msgMap.put("code","0");
            msgMap.put("msg","退款失败,订单不存在");

            payRecord.setMsg("退款失败,订单不存在");
            payRecord.setOutStatus(0);
            payRecord.setBackStatus(0);
            payRecordRepository.save(payRecord);
            return msgMap;
        }
    }

    /**
     * 计算需要退款的子订单集合
     * @param sn
     * @param refundFee
     * @return
     */
    private List<RefundTempParam> checkChildOrderList(String sn,String refundFee){
        QuerySpec querySpec = QuerySpec.matching();
        querySpec.withMatcherValue("orderNo", QuerySpec.EQUAL, sn);
        querySpec.withMatcherValue("status", QuerySpec.EQUAL, "1");
        List<OrderPay> orderPayList = orderPayRepository.findAll(QuerySpec.of(querySpec), Sort.by(Sort.Direction.DESC, "id"));
        //记录需要退款的子订单集合
        List<RefundTempParam> refundTempParamList = new ArrayList<>();

        //初始化一个退款金额
        BigDecimal pre = new BigDecimal(0);
        int num = 0;
        for (OrderPay orderPay : orderPayList) {
            Double price = orderPay.getPrice();
            BigDecimal a = new BigDecimal(price);
            pre = pre.add(a);

            double rf = Double.parseDouble(refundFee);
            BigDecimal b = new BigDecimal(rf);

            //比对退款金额
            if (pre.compareTo(b) == 0) {
                RefundTempParam refundTempParam = new RefundTempParam();
                refundTempParam.setOutTradeNo(orderPay.getOrderChildNo());
                refundTempParam.setTotalFee(String.valueOf(price));
                refundTempParam.setRefundFee(String.valueOf(price));
                refundTempParamList.add(refundTempParam);
                break;
            } else if (pre.compareTo(b) > 0) {
                RefundTempParam refundTempParam = new RefundTempParam();
                refundTempParam.setOutTradeNo(orderPay.getOrderChildNo());
                refundTempParam.setTotalFee(String.valueOf(price));
                //判断如果是第一条就满足  则退款金额就是当前退款金额 如果不是第一条 则退款金额就是除掉之前的部分  退掉剩下的部分
                if (num == 0){
                    refundTempParam.setRefundFee(refundFee);
                }else{
                    //多余的部分就是退款金额
                    BigDecimal result = pre.subtract(b);
                    refundTempParam.setRefundFee(String.valueOf(result));
                }
                refundTempParamList.add(refundTempParam);
                break;
            } else {
                RefundTempParam refundTempParam = new RefundTempParam();
                refundTempParam.setOutTradeNo(orderPay.getOrderChildNo());
                refundTempParam.setTotalFee(String.valueOf(price));
                refundTempParam.setRefundFee(String.valueOf(price));
                refundTempParamList.add(refundTempParam);
                num++;
            }
        }
        return refundTempParamList;
    }






    private String generateNonceStr() {
        return UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);
    }
    private boolean verifySign(Map<String, String> params, String apiKey) throws Exception {
        String sign = params.remove("sign");
        String generatedSign = WXPayUtil.generateSign(params, apiKey);
        return generatedSign.equals(sign);
    }
    private String mapToXml(Map<String, String> map) {
        StringBuilder xmlBuilder = new StringBuilder("<xml>");
        map.forEach((key, value) -> xmlBuilder.append("<").append(key).append(">").append(value).append("</").append(key).append(">"));
        xmlBuilder.append("</xml>");
        String xmlParams = xmlBuilder.toString();
        return xmlParams;
    }


    private Map<String, String> parseXml(String xml) throws DocumentException {
        SAXReader reader = new SAXReader();
        Document document = reader.read(new StringReader(xml));
        Element root = document.getRootElement();
        Map<String, String> map = new HashMap<>();
        for (Iterator<Element> it = root.elementIterator(); it.hasNext(); ) {
            Element e = it.next();
            map.put(e.getName(), e.getText());
        }
        return map;
    }

    private String sendPostRequest(String xmlData, String certPath, String mchId) throws IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException, UnrecoverableKeyException, KeyManagementException {
        HttpPost httpPost = new HttpPost(REFUND_URL);
        httpPost.setEntity(new StringEntity(xmlData, "UTF-8"));
        httpPost.addHeader("Content-Type", "application/xml");
        ClassPathResource resource = new ClassPathResource(certPath);
        KeyStore keyStore = KeyStore.getInstance("PKCS12");
        InputStream inputStream = resource.getInputStream();
        keyStore.load(inputStream, mchId.toCharArray());
        SSLContext sslContext = SSLContexts.custom()
                .loadKeyMaterial(keyStore, mchId.toCharArray())
                .build();
        CloseableHttpClient wxPayHttpClient = HttpClients.custom().setSSLContext(sslContext).build();
        try (CloseableHttpResponse response = wxPayHttpClient.execute(httpPost)) {
            String s = EntityUtils.toString(response.getEntity(), "UTF-8");
            return s;
        }
    }



    /**
     * 发起退款请求
     *
     * @return 退款结果
     */
    private WxPayRefundResult refund(PayParam payParam) throws Exception {

        // 初始化微信支付配置
        WxPayConfig payConfig = new WxPayConfig();
        payConfig.setAppId(payParam.getAppId());
        payConfig.setMchId(payParam.getMchId());
        payConfig.setMchKey(payParam.getApiKey());
        payConfig.setKeyPath(payParam.getKeyPath());

        // 初始化微信支付服务
        WxPayService wxPayService = new WxPayServiceImpl();
        wxPayService.setConfig(payConfig);

        // 构建退款请求对象
        WxPayRefundRequest refundRequest = new WxPayRefundRequest();
        refundRequest.setOutTradeNo(payParam.getOutTradeNo()); // 商户订单号
        refundRequest.setOutRefundNo("REFUND_" + System.currentTimeMillis()); // 商户退款单号
        refundRequest.setTotalFee(Integer.parseInt(payParam.getTotalFee())); // 订单总金额
        refundRequest.setRefundFee(Integer.parseInt(payParam.getRefundFee())); // 退款金额
        refundRequest.setNotifyUrl(payParam.getRefundNotifyUrl()); // 退款回调地址

        // 发起退款请求
        return wxPayService.refundV2(refundRequest);
    }
}
